/*
 * ResourceMonitor.java
 *
 * Created on May 5, 2009, 5:59 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package im.node;

import im.general.BaseAgent;
import im.general.Config;
import im.general.GetCHListBehaviour;
import im.messages.ResourceMessage;
import im.numgenerator.PseudoRandomGenerator;
import im.resources.*;
import im.resources.model.ResourcesDynamicUpdaterBehaviour;
import im.resources.model.ResourcesModel;
import jade.core.AID;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.SequentialBehaviour;
import jade.lang.acl.ACLMessage;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Random;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author bluejack
 */
public class MobileNodeAgent extends BaseAgent{

    MobileNodeInfo mn_info;

    //protected ResourceStatus status;
    //private String container;
    protected String clusterHeadID;

    protected boolean usePublishing;
     //this flag is only used for tracing purposes
    public boolean isShrinkJoin = false;

    public boolean gotCHpromotion = false;
    protected String CHpromotion = null;

    public String getCHpromotion() {
        return CHpromotion;
    }

    public void setCHpromotion(String CHpromotion) {
        this.CHpromotion = CHpromotion;
    }


    /** Creates a new instance of ResourceMonitor */
    @Override
    public void setup() {
        Object [] argv = this.getArguments();
        //container = (String)argv[0];
        //this.usePublishing = ((Boolean)argv[1]).booleanValue();
        this.seedRand = ((Long)argv[1]).longValue();

        this.pr_gen = new PseudoRandomGenerator(this.seedRand);
        this.resources_model = new ResourcesModel(this.pr_gen);
        
        super.setup();
        addBehaviour(new ResourcesDynamicUpdaterBehaviour(this));
        addBehaviour(new PublishSubscribeBehaviour(this));
        addBehaviour(new NotifyListenBehaviour(this));
        
        try {
            if(this.pr_gen.getUniform()<Config.PUBLISHER_PROBABILITY)
                usePublishing=true;
            else
                usePublishing=false;

            //System.out.println("sono publisher? "+usePublishing);

            mn_info = new MobileNodeInfo(this.getName(), null,
                    new ResourceStatus(this.resources_model, this.getContainerName()), this.container );

            setStatus(new ResourceStatus(resources_model, this.getContainerName()));
            addBehaviour(new UpdateResourcesBehaviour(this));
            //addBehaviour(new ResourceListenBehaviour(this));

            SequentialBehaviour join_srv_b = new SequentialBehaviour(this);
            join_srv_b.addSubBehaviour(new GetCHListBehaviour(this));
            join_srv_b.addSubBehaviour(new ClusterJoinReqBehaviour(this));
            addBehaviour(join_srv_b);


            //TODO check if onEnd patch works :D
            SequentialBehaviour new_ch_ntfy_hndl_b = new SequentialBehaviour(this);
            /*
                    {@Override
                             protected Behaviour getCurrent() {
                                if(super.getCurrent()==null)
                                    scheduleFirst();
                                return super.getCurrent();
                            }
                        /*public int onEnd() {
                        this.reset();
                        return 0;}* /
                    }; */

            new_ch_ntfy_hndl_b.addSubBehaviour(new NewCHNotifyListenerBehaviour(this));
            new_ch_ntfy_hndl_b.addSubBehaviour(new ClusterJoinReqBehaviour(this));


            addBehaviour(new_ch_ntfy_hndl_b);

            //addBehaviour(new NewCHNotifyListenerBehaviour(this));

            //addBehaviour(new ClusterShrinkingListenerBehaviour(this));

            SequentialBehaviour cl_shrnk_hndl_b = new SequentialBehaviour(this)
                    {@Override
                             protected Behaviour getCurrent() {
                                if(super.getCurrent()==null)
                                    scheduleFirst();
                                return super.getCurrent();
                            }
                        /*public int onEnd() {
                        this.reset();
                        return 0;}*/
                    };

            cl_shrnk_hndl_b.addSubBehaviour(new ClusterShrinkingListenerBehaviour(this));
            cl_shrnk_hndl_b.addSubBehaviour(new ClusterJoinReqBehaviour(this));

            addBehaviour(cl_shrnk_hndl_b);

        } catch (Exception e) {
            e.printStackTrace();
        }

        //addBehaviour(new ClusterJoinReqBehaviour(this));
        
    }
    
    /*
    public void action() {
        //System.out.println("RM status: "+status);
    }
    */
    public boolean done() {
        return false;
    }

    public void resetNewChHnld() {
        try {
            SequentialBehaviour b = new SequentialBehaviour(this);
            b.addSubBehaviour(new NewCHNotifyListenerBehaviour(this));
            b.addSubBehaviour(new ClusterJoinReqBehaviour(this));
            addBehaviour(b);
        } catch (Exception ex) {
            Logger.getLogger(MobileNodeAgent.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void resetShrinkChHnld() {
        try {
            SequentialBehaviour cl_shrnk_hndl_b = new SequentialBehaviour(this);
            cl_shrnk_hndl_b.addSubBehaviour(new ClusterShrinkingListenerBehaviour(this));
            cl_shrnk_hndl_b.addSubBehaviour(new ClusterJoinReqBehaviour(this));
        } catch (Exception ex) {
            Logger.getLogger(MobileNodeAgent.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    public String getBestCH() {
        //pr_gen
        //TODO: evaluate best ch :)        

        String best = null;
        
        try {

            if( this.CH_idx == null )
                throw new NullPointerException("ERROR: MobileNodeAgent.getBestCH() found CHs=null");

            //System.out.println( "CLI.......\n" + this.CHs.toString() + "\n......");
            /*
            if( this.CHs.size() > 2  ) {
                Random rnd = new Random(System.currentTimeMillis());
                pos = rnd.nextInt(this.CHs.size());
            }


            if ( this.CHs.size() == 2 && Math.random() > 0.5) pos = 1;
             * */
            
            String gCH = findGuestCH();

            if(gCH != null )
                return gCH;

            switch( Config.CLUSTER_POLICY ) {
                case Config.CLUSTER_RANDOM:
                    best = selectRandomCH();
                break;

                case Config.CLUSTER_HEURISTIC:
                    best = selectBestIdxCH();
                break;

                default:
                best = CH_idx.keys().nextElement();
            }

            } catch( Exception e ) {
            e.printStackTrace();
        }

        return best;
    }

    protected String selectRandomCH(){

        int pos = 0;
        Set<String> k = CH_idx.keySet();


        if( k.size() > 2  ) {
            pos = pr_gen.getUniformInt(k.size());
        }

        if ( k.size() == 2 && pr_gen.getUniform() > 0.5) pos = 1;

        return (String) k.toArray()[pos];
    }

    protected String selectBestIdxCH(){

        int pos = 0;
        Enumeration<String> ks = CH_idx.keys();

        double best_idx = -1.0;
        double curr_idx = -1.0;
        String best_ch = null, curr_ch;

        while(ks.hasMoreElements()){
            curr_ch = (String) ks.nextElement();
            curr_idx = CH_idx.get(curr_ch).doubleValue();
            if( curr_idx > best_idx ) {
                best_idx = curr_idx;
                best_ch = curr_ch;
            }
        }
        return best_ch;
    }

    protected String findGuestCH(){

        if ( gotCHpromotion && CH_idx.size() == 2 ) {
             System.out.println("[MobileNodeAgent] Got Promotion: " + this.getLocalName() );
            
            return this.CHpromotion;

        }
        
        return null;

    }

    public ResourceStatus getStatus() {
        return mn_info.getResourceStatus();
    }

    public void setStatus(ResourceStatus status) {
        this.mn_info.setResourceStatus(status);
    }

    @Override
    public boolean updateResources() {

        /* ResuorceStatus rs = this.getStatus();
        String olds = "Ch:"+this.resources_model.getChargeReminderMah()
                    + "\tBw:" + this.resources_model.getFreeBWKbps()
                    + "\tCPU:" + this.resources_model.getFreeCPUMhz()
                    + "\tRAM:" + this.resources_model.getFreeRamMB()
                    + "\tD:" + this.resources_model.getFreeDiskMB();
*/
        boolean wrap_changes = this.resources_model.updateResources();//super.updateResources();
        mn_info.updateResourceStatus(resources_model);
/*

        String news = "Ch:"+this.resources_model.getChargeReminderMah()
                    + "\tBw:" + this.resources_model.getFreeBWKbps()
                    + "\tCPU:" + this.resources_model.getFreeCPUMhz()
                    + "\tRAM:" + this.resources_model.getFreeRamMB()
                    + "\tD:" + this.resources_model.getFreeDiskMB();

        System.out.println("OLD: " +olds + "\nNEW: " + news + "\nR: " + wrap_changes + "\n" );
*/
        if(wrap_changes && clusterHeadID != null  ) {
            try {
                ACLMessage msg = new ACLMessage(ACLMessage.INFORM);
                msg.setEncoding(Config.UPDATE_STATUS_NAME);
                msg.setContentObject(new ResourceMessage(this.getStatus(), this.container));
                msg.addReceiver(new AID(this.clusterHeadID, AID.ISLOCALNAME));
                this.send(msg);
            } catch (Exception ex) {
                Logger.getLogger(MobileNodeAgent.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        return wrap_changes;
    }

    public String getClusterHeadID() {
        return clusterHeadID;
    }

    public Double getClusterHeadIdx() {
        return CH_idx.get(clusterHeadID);
    }

    public void setClusterHeadID(String clusterHeadID) {
        this.clusterHeadID = clusterHeadID;
    }

    public MobileNodeInfo getMn_info() {
        mn_info.updateResourceStatus(this.resources_model);
        return mn_info;
    }

    public void setMn_info(MobileNodeInfo mn_info) {
        this.mn_info = mn_info;
    }

    public PseudoRandomGenerator getPrg() {
        return pr_gen;
    }

    @Override
    public int getAttachedNodes() {
        return resources_model.getAttachedNodes();
    }

    @Override
    public void setAttachedNodes(int attachedNodes) {
        resources_model.setAttachedNodes(attachedNodes);
    }
    
}
